﻿using Core.Entities;
using Core.Enums;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Core.Tools
{
    public static class TypeHelper
    {
        private static readonly Type RuntimeTypeType = typeof(object).GetType();

        private delegate int SizeOfHelperDelegate(Type t, bool throwIfNotMarshalable);
        private static readonly SizeOfHelperDelegate MarshalSizeOfHelper;

        private unsafe delegate void InstantiateDelegate(RuntimeTypeHandle Handle, IntPtr* GenericArgs, int GenericArgsLength, out Type LpType);
        private static readonly InstantiateDelegate RuntimeTypeHandleInstantiate;

        private delegate object AllocateDelegate(Type RuntimeType);
        private static readonly AllocateDelegate RuntimeTypeHandleAllocate;

        private delegate object MemberwiseCloneDelegate(object obj);
        private static readonly MemberwiseCloneDelegate ObjectMemberwiseClone;


        static TypeHelper()
        {
            var MarshalSizeOfHelperMethod = typeof(Marshal).GetMethod("SizeOfHelper", BindingFlags.NonPublic | BindingFlags.Static);

            MarshalSizeOfHelper = (SizeOfHelperDelegate)Delegate.CreateDelegate(typeof(SizeOfHelperDelegate), MarshalSizeOfHelperMethod);



            var RuntimeTypeHandleInstantiateMethod = typeof(RuntimeTypeHandle).GetMethod("Instantiate", BindingFlags.NonPublic | BindingFlags.Static);

            RuntimeTypeHandleInstantiate = Pointer.Cast<InstantiateDelegate>(MethodHelper.CreateDelegate(RuntimeTypeHandleInstantiateMethod));


            var RuntimeTypeHandleAllocateeMethod = typeof(RuntimeTypeHandle).GetMethod("Allocate", BindingFlags.NonPublic | BindingFlags.Static);

            RuntimeTypeHandleAllocate = Pointer.Cast<AllocateDelegate>(MethodHelper.CreateDelegate(RuntimeTypeHandleAllocateeMethod));

            var ObjectMemberwiseCloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
            ObjectMemberwiseClone = (MemberwiseCloneDelegate)Delegate.CreateDelegate(typeof(MemberwiseCloneDelegate), ObjectMemberwiseCloneMethod);
        }

        public unsafe static int SizeOf(Type Type)
        {
            if (Type == null)
            {
                throw new NullReferenceException("Type");
            }

            if (Type.IsClass)
            {
                if (Type == typeof(object))
                {
                    return sizeof(IntPtr);
                }

                var Fields = Type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

                if (Fields.Length == 0)
                {
                    return SizeOf(Type.BaseType);
                }

                int Right = Fields.Length - 1;
                int MaxOffset = OffsetOf(Fields[Right]);
                var FieldType = Fields[Right].FieldType;

                while (--Right >= 0)
                {
                    int Offset = OffsetOf(Fields[Right]);

                    if (Offset > MaxOffset)
                    {
                        MaxOffset = Offset;
                        FieldType = Fields[Right].FieldType;
                    }
                }

                // = last field offset + last field size + type handle size;
                return MaxOffset + (FieldType.IsValueType ? SizeOf(FieldType) : sizeof(IntPtr)) + sizeof(IntPtr);
            }

            return MarshalSizeOfHelper(Type, false);
        }

        /// <summary>
        /// 获得字段的内存偏移量，不考虑 class 的 TypeHandle，即如果这个字段是第一个字段，那么就返回 0。
        /// </summary>
        /// <param name="Field">字段</param>
        /// <returns>返回一个 int 值</returns>
        public unsafe static int OffsetOf(FieldInfo Field)
        {
            if (Field == null)
            {
                throw new NullReferenceException("Field");
            }

            var LpFieldHandleInfo = (FieldHandleStruct*)Field.FieldHandle.Value;

            return LpFieldHandleInfo->Offset;
        }

        public unsafe static Type MakeGenericType(Type Type, Type GenericType1)
        {
            var GenericType1Handle = GenericType1.TypeHandle.Value;

            Type Result;

            RuntimeTypeHandleInstantiate(Type.TypeHandle, &GenericType1Handle, 1, out Result);

            return Result;
        }

        public unsafe static Type MakeGenericType(Type Type, Type GenericType1, Type GenericType2)
        {
            var GenericTypeHandles = stackalloc IntPtr[2];

            GenericTypeHandles[0] = GenericType1.TypeHandle.Value;
            GenericTypeHandles[1] = GenericType2.TypeHandle.Value;

            Type Result;

            RuntimeTypeHandleInstantiate(Type.TypeHandle, GenericTypeHandles, 2, out Result);

            return Result;
        }

        public unsafe static Type MakeGenericType(Type Type, params Type[] GenericTypes)
        {
            var GenericTypeHandles = stackalloc IntPtr[GenericTypes.Length];

            for (int i = 0; i < GenericTypes.Length; i++)
            {
                GenericTypeHandles[i] = GenericTypes[i].TypeHandle.Value;
            }

            Type Result;

            RuntimeTypeHandleInstantiate(Type.TypeHandle, GenericTypeHandles, GenericTypes.Length, out Result);

            return Result;
        }

        public static object Allocate(Type Type)
        {
            if (!RuntimeTypeType.IsInstanceOfType(Type))
            {
                throw new ArgumentException("type is not a runtime type.");
            }

            return RuntimeTypeHandleAllocate(Type);
        }

        public static Type[] GetParamsTypes(ParameterInfo[] Params)
        {
            return ArrayHelper.Filter(Params, Item => true, Item => Item.ParameterType);
        }

        public static Type[] UnRefParams(Type[] ParamsTypes)
        {
            for (int i = 0; i < ParamsTypes.Length; i++)
            {
                if (ParamsTypes[i].IsByRef)
                {
                    /* 引用传递的参数 */
                    ParamsTypes[i] = ParamsTypes[i].GetElementType();
                }
                else
                {
                    ParamsTypes[i] = ParamsTypes[i];
                }
            }

            return ParamsTypes;
        }

        public static bool ParamsCompares(Type[] Params, object[] InputParams)
        {
            for (int i = 0; i < Params.Length; i++)
            {
                if (Params[i].IsInstanceOfType(InputParams[i]) || (Params[i].IsByRef && Params[i].GetElementType().IsInstanceOfType(InputParams[i])))
                {
                    continue;
                }

                return false;
            }

            return true;
        }

        public static bool ParamsCompares(Type[] Params, Type[] InputParams)
        {
            for (int i = 0; i < Params.Length; i++)
            {
                if (Params[i] != InputParams[i])
                {
                    return false;
                }
            }

            return true;
        }

        public static void EachMembers<T>(Type InternalType, Func<Type, T[]> GetMembers, Action<T> Callback)
        {
            if (InternalType == null)
            {
                return;
            }

            EachMembers(InternalType.BaseType, GetMembers, Callback);

            foreach (var Item in GetMembers(InternalType))
            {
                Callback(Item);
            }
        }

        public static IEnumerable<MethodInfo> GetMethodsIsNotGenericAndRemovedDuplicateSignaturesAndProperties(Type Type, BindingFlags Flags)
        {
            // TODO 算法优化

            var MethodsMap = new Dictionary<MethodSign, MethodInfo>();

            EachMembers(
                Type,
                InternalType => InternalType.GetMethods(Flags),
                Item =>
                {
                    if (Item.IsGenericMethod)
                    {
                        return;
                    }

                    var MethodSign = new MethodSign(Item.Name, GetParamsTypes(Item.GetParameters()), typeof(void));

                    MethodsMap[MethodSign] = Item;
                }
            );

            var MethodsMap2 = new Dictionary<MethodInfo, bool>();

            foreach (var Item in MethodsMap)
            {
                MethodsMap2.Add(Item.Value, true);
            }

            Flags |= BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly;

            EachMembers(
                Type,
                InternalType => InternalType.GetProperties(Flags),
                Item =>
                {
                    var GetMethod = Item.GetGetMethod(true);
                    var SetMethod = Item.GetSetMethod(true);

                    if (GetMethod != null)
                    {
                        MethodsMap2.Remove(GetMethod);
                    }

                    if (SetMethod != null)
                    {
                        MethodsMap2.Remove(SetMethod);
                    }
                }
            );

            var Result = new MethodInfo[MethodsMap2.Count];

            MethodsMap2.Keys.CopyTo(Result, 0);

            return Result;
        }

        public static T Clone<T>(T Object)
        {
            if (typeof(T).IsValueType)
            {
                return Object;
            }

            if (Object == null)
            {
                return default(T);
            }

            if (Object is string)
            {
                return Object;
            }

            if (Object is ICloneable)
            {
                return (T)((ICloneable)Object).Clone();
            }

            return (T)ObjectMemberwiseClone(Object);
        }


        public static object CastParameterType(object Value, ParamTypes DestineType)
        {
            switch (DestineType)
            {
                case ParamTypes.Boolean:
                    if (!(Value is bool))
                    {
                        return Convert.ToBoolean(Value);
                    }
                    break;
                case ParamTypes.Int:
                    if (!(Value is int))
                    {
                        return Convert.ToInt32(Value);
                    }
                    break;
                case ParamTypes.Long:
                    if (!(Value is long))
                    {
                        return Convert.ToInt64(Value);
                    }
                    break;
                case ParamTypes.Float:
                    if (!(Value is float))
                    {
                        return Convert.ToSingle(Value);
                    }
                    break;
                case ParamTypes.Double:
                    if (!(Value is double))
                    {
                        return Convert.ToDouble(Value);
                    }
                    break;
                case ParamTypes.String:
                    if (!(Value is string))
                    {
                        return Convert.ToString(Value);
                    }
                    break;
                case ParamTypes.DateTime:
                    if (!(Value is DateTime))
                    {
                        return Convert.ToDateTime(Value);
                    }
                    break;
                case ParamTypes.TimeSpan:
                    if (!(Value is TimeSpan))
                    {
                        return TimeSpan.Parse((string)Value);
                    }
                    break;
                case ParamTypes.Guid:
                    if (!(Value is Guid))
                    {
                        return Guid.Parse((string)Value);
                    }
                    break;
                case ParamTypes.Ids:
                    if (!(Value is Array))
                    {
                        if (Value is string)
                        {
                            return ArrayHelper.ParseArray((string)Value);
                        }
                        else
                        {
                            return new object[] { Value };
                        }
                    }
                    break;
            }

            return Value;
        }
    }
}